//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <ddk.h>
#include <misc.h>
#include <ktime.h>

/*
 * This is the format of the contents of the deTime field in the direntry
 * structure.
 * We don't use bitfields because we don't know how compilers for
 * arbitrary machines will lay them out.
 */
#define DT_2SECONDS_MASK    0x1F    /* seconds divided by 2 */
#define DT_2SECONDS_SHIFT   0
#define DT_MINUTES_MASK     0x7E0    /* minutes */
#define DT_MINUTES_SHIFT    5
#define DT_HOURS_MASK       0xF800    /* hours */
#define DT_HOURS_SHIFT      11

/*
 * This is the format of the contents of the deDate field in the direntry
 * structure.
 */
#define DD_DAY_MASK         0x1F    /* day of month */
#define DD_DAY_SHIFT        0
#define DD_MONTH_MASK       0x1E0    /* month */
#define DD_MONTH_SHIFT      5
#define DD_YEAR_MASK        0xFE00    /* year - 1980 */
#define DD_YEAR_SHIFT       9

/*
 * The number of seconds between Jan 1, 1970 and Jan 1, 1980. In that
 * interval there were 8 regular years and 2 leap years.
 */
#define    SECONDSTO1980    (((8 * 365) + (2 * 366)) * (24 * 60 * 60))

/*
 * Days in each month in a regular year.
 */
const UInt16 regyear[] = {
    31, 28, 31, 30, 31, 30,
    31, 31, 30, 31, 30, 31
};

/*
 * Days in each month in a leap year.
 */
const UInt16 leapyear[] = {
    31, 29, 31, 30, 31, 30,
    31, 31, 30, 31, 30, 31
};

UInt32 lasttime;
UInt32 lastday;
UInt16 lastddate;
UInt16 lastdtime;
UInt16 lastdosdate;
UInt32 lastseconds;

timezone tz = {-480, 0};            /* XXX */

/*
 * Convert the unix version of time to dos's idea of time to be used in
 * file timestamps. The passed in unix time is assumed to be in GMT.
 */
EXTERN_C Void unix2dostime(
    timespec_o *tsp,
    UInt16 *ddp,
    UInt16 *dtp,
    UInt8 *dhp)
{
    UInt32 t;
    UInt32 days;
    UInt32 inc;
    UInt32 year;
    UInt32 month;
    const UInt16 *months;

    /*
     * If the time from the last conversion is the same as now, then
     * skip the computations and use the saved result.
     */
    t = tsp->tv_sec - (tz.tz_minuteswest * 60)
         /* +- daylight saving time correction */ ;
    t &= ~1;
    if (lasttime != t) {
        lasttime = t;
        lastdtime = (UInt16)((((t / 2) % 30) << DT_2SECONDS_SHIFT)
            + (((t / 60) % 60) << DT_MINUTES_SHIFT)
            + (((t / 3600) % 24) << DT_HOURS_SHIFT));

        /*
         * If the number of days since 1970 is the same as the last
         * time we did the computation then skip all this leap year
         * and month stuff.
         */
        days = t / (24 * 60 * 60);
        if (days != lastday) {
            lastday = days;
            for (year = 1970;; year++) {
                inc = year & 0x03 ? 365 : 366;
                if (days < inc)
                    break;
                days -= inc;
            }
            months = year & 0x03 ? regyear : leapyear;
            for (month = 0; month < 12; month++) {
                if (days < months[month])
                    break;
                days -= months[month];
            }
            lastddate = (UInt16)(((days + 1) << DD_DAY_SHIFT)
                + ((month + 1) << DD_MONTH_SHIFT));
            /*
             * Remember dos's idea of time is relative to 1980.
             * unix's is relative to 1970.  If somehow we get a
             * time before 1980 then don't give totally crazy
             * results.
             */
            if (year > 1980)
                lastddate += (UInt16)((year - 1980) << DD_YEAR_SHIFT);
        }
    }

    if (dtp != NULL)
        *dtp = lastdtime;
    if (dhp != NULL)
        *dhp = (UInt8)((tsp->tv_sec & 1) * 100 + tsp->tv_nsec / 10000000);

    *ddp = lastddate;
}

EXTERN_C Void dos2unixtime(
    UInt32 dd,
    UInt32 dt,
    UInt32 dh,
    timespec_o *tsp)
{
    UInt32 seconds;
    UInt32 m, month;
    UInt32 y, year;
    UInt32 days;
    const UInt16 *months;
    if (dd == 0) {
        /*
         * Uninitialized field, return the epoch.
         */
        tsp->tv_sec = 0;
        tsp->tv_nsec = 0;
        return;
    }
    seconds = ((dt & DT_2SECONDS_MASK) >> DT_2SECONDS_SHIFT) * 2
        + ((dt & DT_MINUTES_MASK) >> DT_MINUTES_SHIFT) * 60
        + ((dt & DT_HOURS_MASK) >> DT_HOURS_SHIFT) * 3600
        + dh / 100;
    /*
     * If the year, month, and day from the last conversion are the
     * same then use the saved value.
     */
    if (lastdosdate != dd) {
        lastdosdate = dd;
        days = 0;
        year = (dd & DD_YEAR_MASK) >> DD_YEAR_SHIFT;
        for (y = 0; y < year; y++)
            days += y & 0x03 ? 365 : 366;
        months = year & 0x03 ? regyear : leapyear;
        /*
         * Prevent going from 0 to 0xffffffff in the following
         * loop.
         */
        month = (dd & DD_MONTH_MASK) >> DD_MONTH_SHIFT;
        if (month == 0) {
            DPRINTF(("dos2unixtime(): month value out of range (%ld)\n", \
                month));
            month = 1;
        }
        for (m = 0; m < month - 1; m++)
            days += months[m];
        days += ((dd & DD_DAY_MASK) >> DD_DAY_SHIFT) - 1;
        lastseconds = (days * 24 * 60 * 60) + SECONDSTO1980;
    }
    tsp->tv_sec = seconds + lastseconds + (tz.tz_minuteswest * 60)
         /* -+ daylight saving time correction */ ;
    tsp->tv_nsec = (dh % 100) * 10000000;

    if (tsp->tv_sec < 0) tsp->tv_sec = 0;
}
